package apiserver

import (
	"git.hortorgames.com/orange/tang/env"
	"git.hortorgames.com/orange/tang/http"
	"git.hortorgames.com/orange/tang/log"
	"git.hortorgames.com/orange/tang/reflectex"
	"git.hortorgames.com/orange/tang/utils"
	"github.com/gin-gonic/gin"
	"reflect"
	"strings"
	"time"
)

type Request struct {
	GameId   string      `json:"gameId,omitempty"`
	PlayerId int64       `json:"playerId,omitempty"`
	Token    string      `json:"token,omitempty"`
	Seq      int         `json:"seq,omitempty"`
	Body     interface{} `json:"body,omitempty"`
}

func (gs *Server) RegisterController(handlers ...http.IHttpController) {
	if len(handlers) == 0 {
		return
	}

	midCtx := func(c *gin.Context) {
		ctx := &Context{
			Context:     &http.Context{Context: c},
			RequestTime: time.Now(),
		}
		c.Set("_ctx", ctx)
		c.Next()
	}

	var mids []gin.HandlerFunc
	for i := 0; i < len(handlers)-1; i++ {
		h := handlers[i]
		t := reflect.TypeOf(h)
		v := reflect.ValueOf(h)
		ti := reflectex.TypeInfoOf(t)
		for _, _mi := range ti.Methods {
			mi := _mi
			if !mi.Public || mi.Method.Type.NumIn() != 2 || mi.Method.Type.In(1) != reflect.TypeOf((*Context)(nil)) || mi.GetTagByTypeInfo(http.TypeInfoOfHttpMidMethodTag) == nil {
				continue
			}
			mid := func(c *gin.Context) {
				ctx := c.MustGet("_ctx").(*Context)
				utils.Try(func() {
					ps := []reflect.Value{reflect.ValueOf(ctx)}
					v.MethodByName(mi.Name).Call(ps)
					ctx.Next()
				}, func(e interface{}) {
					ctx.Logger().Error("http error", "err", e)
					ctx.Abort()
					return
				})
			}
			mids = append(mids, mid)
		}
	}

	h := handlers[len(handlers)-1]
	t := reflect.TypeOf(h)
	v := reflect.ValueOf(h)
	ti := reflectex.TypeInfoOf(t)
	for _, _mi := range ti.Methods {
		mi := _mi
		if !mi.Public || mi.Method.Type.NumIn() < 2 || mi.Method.Type.NumIn() > 3 || mi.Method.Type.In(1) != reflect.TypeOf((*Context)(nil)) {
			continue
		}
		tag, ok := mi.GetTagByTypeInfo(http.TypeInfoOfHttpMethodTag).(*http.HttpMethodTag)
		if !ok || len(tag.Methods) == 0 || tag.Route == "" {
			continue
		}

		midReq := func(c *gin.Context) {
			ctx := c.MustGet("_ctx").(*Context)
			utils.Try(func() {
				ctx.RequestData = &Request{}
				var pv reflect.Value
				if mi.Method.Type.NumIn() == 3 {
					pt := mi.Method.Type.In(2)
					pv = reflect.New(pt)
					ctx.RequestData.Body = pv.Interface()
				}
				err := Decode(ctx)
				if err != nil {
					ctx.Logger().Error("http decode error", "err", err.Error())
					ctx.Abort()
					return
				}
				if mi.Method.Type.NumIn() == 3 {
					ctx.RequestData.Body = pv.Elem().Interface()
				}
				// 临时兼容
				//if ctx.RequestData.GameId == "" {
				//	back := ctx.RequestData
				//	body := map[string]interface{}{}
				//	ctx.RequestData = &Request{Body: &body}
				//	err := Decode(ctx)
				//	if err != nil {
				//		ctx.Abort()
				//		return
				//	}
				//	ctx.RequestData = back
				//	ctx.RequestData.GameId, _ = body["gameId"].(string)
				//	if pid, ok := body["playerId"]; ok {
				//		switch pid.(type) {
				//		case int64:
				//			ctx.RequestData.PlayerId = pid.(int64)
				//		case int32:
				//			ctx.RequestData.PlayerId = int64(pid.(int32))
				//		}
				//	}
				//	ctx.RequestData.Token, _ = body["token"].(string)
				//}
				ctx.SetLogger(ctx.Logger().With("api", ctx.Request().URL.Path, "playerId", ctx.RequestData.PlayerId, "seq", ctx.RequestData.Seq, "token", ctx.RequestData.Token))
				ctx.Next()
			}, func(e interface{}) {
				ctx.Logger().Error("http error", "err", e)
				ctx.Abort()
			})

		}

		handle := func(c *gin.Context) {
			ctx := c.MustGet("_ctx").(*Context)
			c.Set("_ctx", nil)
			utils.Try(func() {
				ps := []reflect.Value{reflect.ValueOf(ctx)}
				if mi.Method.Type.NumIn() == 3 {
					ps = append(ps, reflect.ValueOf(ctx.RequestData.Body))
				}
				ctx.Logger().Info("[Req]", "req", utils.JsonEncodeToStringMust(ctx.RequestData.Body))
				v.MethodByName(mi.Name).Call(ps)
				ctx.Done()
			}, func(e interface{}) {
				ctx.Logger().Error("http error", "err", e)
				ctx.Abort()
			})
		}

		hs := make([]gin.HandlerFunc, 0, len(mids)+3)
		hs = append(hs, midCtx, midReq)
		hs = append(hs, mids...)
		hs = append(hs, handle)
		for _, m := range tag.Methods {
			if env.IsRelease() && m != "POST" {
				continue
			}
			gs.Router.Handle(m, tag.Route, hs...)
		}
	}
}

func Decode(ctx *Context) (err error) {
	defer func() {
		if err != nil {
			log.Error("decode params error", "err", err.Error())
		}
	}()

	if ctx.Request().Method == "GET" || ctx.ContentType() == "application/x-www-form-urlencoded" {
		err = ctx.Request().ParseForm()
		if err != nil {
			return
		}
		tmp := map[string]interface{}{}
		for k, v := range ctx.Request().Form {
			if len(v) > 0 {
				vv := v[0]
				if strings.HasPrefix(vv, "{") || strings.HasPrefix(vv, "[") {
					var obj interface{}
					err := utils.JsonDecodeString(vv, &obj)
					if err == nil {
						tmp[k] = obj
						continue
					}
				}
				if strings.HasPrefix(vv, "~") {
					vv = vv[1:]
				}
				tmp[k] = vv
			}
		}
		json := utils.JsonEncodeToStringMust(tmp)
		err = utils.JsonDecodeString(json, &ctx.RequestData)
	} else if ctx.ContentType() == "application/json" {
		var bytes []byte
		bytes, err = ctx.GetRawData()
		if err != nil {
			return
		}
		err = utils.JsonDecode(bytes, &ctx.RequestData)
	} else if ctx.ContentType() == "application/octet-stream" {
		var bytes []byte
		bytes, err = ctx.GetRawData()
		if err != nil {
			return
		}
		if len(bytes) > 2 && bytes[0] == 'p' && bytes[1] == 'k' {
			bytes, err = utils.GzipXorDecode(bytes)
			ctx.UseXor = true
		} else {
			bytes, err = utils.GzipDecode(bytes)
		}
		if err != nil {
			return
		}
		err = utils.BonDecode(bytes, ctx.RequestData)
		if err != nil {
			return
		}
	}
	return
}
